/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Graphics.PackedVector;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;

namespace Cobalt.Content.Pipeline.Quake.Shaders
{
	internal class ShaderCompiler
	{
		#region Sampler Allocator/Compiler

		private class SamplerCompiler
		{
			public class SamplerState
			{
				public TextureObject Texture;
				public TextureAddressMode AddressMode;

				public SamplerState( TextureObject texture, TextureAddressMode addressMode )
				{
					Texture = texture;
					AddressMode = addressMode;
				}

				public override int GetHashCode()
				{
					return Texture.GetHashCode() ^ AddressMode.GetHashCode();
				}

				public override bool Equals( object obj )
				{
					try
					{
						SamplerState other = (SamplerState)obj;

						return other.Texture == Texture &&
							other.AddressMode == AddressMode;
					}
					catch( InvalidCastException )
					{
						return false;
					}
				}
			}

			public class TextureObject
			{
				public string Name;
				public string DimensionBindingName;
			}

			private Dictionary<string, TextureObject> textureObjects = new Dictionary<string, TextureObject>();
			private Dictionary<SamplerState, string> samplerObjects = new Dictionary<SamplerState, string>();

			private Dictionary<TextureBundle, TextureObject> textureBindings = new Dictionary<TextureBundle, TextureObject>();
			public Dictionary<TextureBundle, TextureObject> TextureBindings { get { return textureBindings; } }

			public TextureObject GetTextureObject( TextureBundle bundle )
			{
				if( bundle.IsConstantBundle() )
					throw new InvalidOperationException( "Constant bundles cannot be compiled into texture objects." );

				string textureKey;
				string textureName = null;

				if( bundle.Images.Count == 1 )
				{
					switch( bundle.Images[0] )
					{
					case TextureBundle.Lightmap:
						textureKey = "Lightmap";
						textureName = "Lightmap";
						break;

					case TextureBundle.TextureImage:
						textureKey = "Diffuse";
						textureName = "Diffuse";
						break;

					default:
						textureKey = bundle.Images[0];
						break;
					}
				}
				else
				{
					StringBuilder sb = new StringBuilder();
					foreach( string img in bundle.Images )
						sb.AppendLine( img );
					textureKey = sb.ToString();
				}

				TextureObject ret;
				if( !textureObjects.TryGetValue( textureKey, out ret ) )
				{
					ret = new TextureObject();
					ret.Name = textureName != null ? textureName : string.Format( "tex_{0}", textureObjects.Count );

					textureObjects.Add( textureKey, ret );
				}

				textureBindings[bundle] = ret;

				return ret;
			}

			public string GetSamplerName( TextureBundle bundle )
			{
				if( bundle.IsConstantBundle() )
					throw new InvalidOperationException( "Constant bundles cannot be compiled into samplers." );

				TextureObject texObj = GetTextureObject( bundle );

				string samplerName;
				SamplerState sampler = new SamplerState( texObj, bundle.AddressMode );

				if( !samplerObjects.TryGetValue( sampler, out samplerName ) )
				{
					samplerName = string.Format( "samp_{0}", samplerObjects.Count );
					samplerObjects.Add( sampler, samplerName );
				}

				return samplerName;
			}

			public string GetBumpSampler( string bumpTextureName, out TextureObject textureObject )
			{
				BumpTextureBundle bumpBundle = new BumpTextureBundle( bumpTextureName );

				textureObject = GetTextureObject( bumpBundle );

				if( textureObject.DimensionBindingName == null )
					textureObject.DimensionBindingName = string.Format( "{0}_Size", textureObject.Name );

				string samplerName;
				SamplerState sampler = new SamplerState( textureObject, bumpBundle.AddressMode );

				if( !samplerObjects.TryGetValue( sampler, out samplerName ) )
				{
					samplerName = string.Format( "samp_{0}", samplerObjects.Count );
					samplerObjects.Add( sampler, samplerName );
				}

				return samplerName;
			}

			public void EmitShaderText( EffectWriter effect )
			{
				foreach( var tex in textureObjects.Values )
				{
					effect.WriteLine( "texture {0};", tex.Name );

					if( tex.DimensionBindingName != null )
					{
						effect.WriteLine( "float2 {0} < string BindTexDimension = \"{1}\"; >;", tex.DimensionBindingName, tex.Name );
					}
				}

				foreach( var sampler in samplerObjects )
				{
					effect.WriteLine( "sampler {0} = sampler_state {{ Texture = <{1}>; AddressU = {2}; AddressV = {2}; MinFilter = Linear; MagFilter = Linear; MipFilter = Linear; }};",
						sampler.Value, sampler.Key.Texture.Name, sampler.Key.AddressMode );
				}
			}
		}

		#endregion

		#region Register Allocator

		[Serializable, Flags]
		private enum Swizzle
		{
			Empty = 0,

			X = 1,
			Y = 2,
			Z = 4,
			W = 8,
			XY = X | Y,
			ZW = Z | W,
			XYZ = X | Y | Z,
			XYZW = X | Y | Z | W,
		}

		[Serializable]
		private struct Register
		{
			public int Index;
			public Swizzle Swizzle;

			public Register( int index, Swizzle swizzle )
			{
				this.Index = index;
				this.Swizzle = swizzle;
			}

			public string ToString( string baseName )
			{
				if( Swizzle == Swizzle.Empty )
					return "Invalid register swizzle.";

				StringBuilder ret = new StringBuilder();

				ret.Append( baseName );
				ret.Append( Index );

				if( Swizzle != Swizzle.XYZW )
				{
					ret.Append( "." );

					if( (Swizzle & Swizzle.X) != Swizzle.Empty )
						ret.Append( "x" );

					if( (Swizzle & Swizzle.Y) != Swizzle.Empty )
						ret.Append( "y" );

					if( (Swizzle & Swizzle.Z) != Swizzle.Empty )
						ret.Append( "z" );

					if( (Swizzle & Swizzle.W) != Swizzle.Empty )
						ret.Append( "w" );
				}

				return ret.ToString();
			}

			public override string ToString()
			{
				return ToString( "r" );
			}
		}

		private class RegisterAllocator
		{
			private Swizzle[] allocated;

			public RegisterAllocator( int numRegisters )
			{
				allocated = new Swizzle[numRegisters];
			}

			public RegisterAllocator Clone()
			{
				RegisterAllocator ret = new RegisterAllocator( MaxRegisters );
				Array.Copy( allocated, ret.allocated, MaxRegisters );
				return ret;
			}

			public int MaxRegisters { get { return allocated.Length; } }

			public Register? Allocate( int elements )
			{
				if( elements == 1 )
				{
					for( int i = 0; i < allocated.Length; i++ )
					{
						if( allocated[i] == Swizzle.XYZ )
						{
							allocated[i] = Swizzle.XYZW;
							return new Register( i, Swizzle.W );
						}
					}
				}

				for( int i = 0; i < allocated.Length; i++ )
				{
					switch( elements )
					{
					case 1:
						if( (allocated[i] & Swizzle.X) == Swizzle.Empty )
						{
							allocated[i] |= Swizzle.X;
							return new Register( i, Swizzle.X );
						}

						if( (allocated[i] & Swizzle.Y) == Swizzle.Empty )
						{
							allocated[i] |= Swizzle.Y;
							return new Register( i, Swizzle.Y );
						}

						if( (allocated[i] & Swizzle.Z) == Swizzle.Empty )
						{
							allocated[i] |= Swizzle.Z;
							return new Register( i, Swizzle.Z );
						}

						if( (allocated[i] & Swizzle.W) == Swizzle.Empty )
						{
							allocated[i] |= Swizzle.W;
							return new Register( i, Swizzle.W );
						}

						break;

					case 2:
						if( (allocated[i] & Swizzle.XY) == Swizzle.Empty )
						{
							allocated[i] |= Swizzle.XY;
							return new Register( i, Swizzle.XY );
						}

						if( (allocated[i] & Swizzle.ZW) == Swizzle.Empty )
						{
							allocated[i] |= Swizzle.ZW;
							return new Register( i, Swizzle.ZW );
						}

						break;

					case 3:
						if( (allocated[i] & Swizzle.XYZ) == Swizzle.Empty )
						{
							allocated[i] |= Swizzle.XYZ;
							return new Register( i, Swizzle.XYZ );
						}

						break;

					case 4:
						if( (allocated[i] & Swizzle.XYZW) == Swizzle.Empty )
						{
							allocated[i] |= Swizzle.XYZW;
							return new Register( i, Swizzle.XYZW );
						}

						break;
					}
				}

				return null;
			}

			public void Deallocate( Register register )
			{
				allocated[register.Index] &= ~register.Swizzle;
			}

			public IEnumerable<Register> UsedRegisters
			{
				get
				{
					for( int i = 0; i < allocated.Length; i++ )
					{
						if( allocated[i] != Swizzle.Empty )
							yield return new Register( i, allocated[i] );
					}
				}
			}
		}

		#endregion

		private class PassCompiler
		{
			#region Helper Types

			private class TexCoordSource
			{
				public TexCoordGen Generator;
				public List<TexCoordMod> Modifiers;

				public TexCoordSource( TexCoordGen generator, IEnumerable<TexCoordMod> modifiers )
				{
					Generator = generator;
					Modifiers = new List<TexCoordMod>( modifiers );
				}

				public TexCoordSource( TextureBundle bundle )
				{
					Generator = bundle.TexCoordGen;
					Modifiers = new List<TexCoordMod>( bundle.TexCoordMods );
				}

				public TexCoordSource( TexCoordGen generator )
				{
					Generator = generator;
					Modifiers = new List<TexCoordMod>();
				}

				public class Comparer : IEqualityComparer<TexCoordSource>
				{

					#region IEqualityComparer<TexCoordSource> Members

					public bool Equals( TexCoordSource x, TexCoordSource y )
					{
						if( x == null )
							return y == null;
						else if( y == null )
							return false;

						if( !x.Generator.Equals( y.Generator ) )
							return false;

						if( x.Modifiers.Count != y.Modifiers.Count )
							return false;

						for( int i = 0; i < x.Modifiers.Count; i++ )
							if( !x.Modifiers[i].Equals( y.Modifiers[i] ) )
								return false;

						return true;
					}

					public int GetHashCode( TexCoordSource obj )
					{
						int ret = obj.Generator.GetHashCode();

						foreach( TexCoordMod mod in obj.Modifiers )
							ret ^= mod.GetHashCode();

						return ret;
					}

					#endregion

					public static readonly Comparer Default = new Comparer();
				}
			}

			private class TexLookupSource : TexCoordSource
			{
				public string Sampler;

				public TexLookupSource( TextureBundle bundle, string sampler )
					: base( bundle )
				{
					this.Sampler = sampler;
				}

				public new class Comparer : IEqualityComparer<TexLookupSource>
				{

					#region IEqualityComparer<TexCoordSource> Members

					public bool Equals( TexLookupSource x, TexLookupSource y )
					{
						if( x == null )
							return y == null;
						else if( y == null )
							return false;

						if( !x.Generator.Equals( y.Generator ) )
							return false;

						if( x.Modifiers.Count != y.Modifiers.Count )
							return false;

						for( int i = 0; i < x.Modifiers.Count; i++ )
							if( !x.Modifiers[i].Equals( y.Modifiers[i] ) )
								return false;

						if( x.Sampler != y.Sampler )
							return false;

						return true;
					}

					public int GetHashCode( TexLookupSource obj )
					{
						int ret = obj.Generator.GetHashCode();

						foreach( TexCoordMod mod in obj.Modifiers )
							ret ^= mod.GetHashCode();

						ret += obj.Sampler.GetHashCode();

						return ret;
					}

					#endregion

					public static readonly Comparer Default = new Comparer();
				}
			}

			private enum InterpolatorSet
			{
				None,

				Color,
				TexCoord,
			}

			private class ColorSource
			{
				public ColorGen RgbGen;
				public AlphaGen AGen;

				public bool InvertRgb, InvertAlpha;

				public ColorSource( ColorGen rgbGen, AlphaGen aGen )
				{
					this.RgbGen = rgbGen;
					this.AGen = aGen;

					//color value invert is always done in pixel shader,
					//vertex color can pass through unmolested in all cases
					if( RgbGen is VertexColorGen && (RgbGen as VertexColorGen).InvertValue )
					{
						InvertRgb = true;
						RgbGen = new VertexColorGen();
					}

					if( AGen is VertexAlphaGen && (AGen as VertexAlphaGen).InvertValue )
					{
						InvertAlpha = true;
						AGen = new VertexAlphaGen();
					}
				}

				public bool IsConstantRgb()
				{
					if( RgbGen is ConstantColorGen )
						return true;

					if( RgbGen is WaveColorGen && IsConstantWave( ((WaveColorGen)RgbGen).Waveform ) )
						return true;

					return false;
				}

				public bool IsGrayRgb()
				{
					return RgbGen is WaveColorGen;
				}

				public bool IsConstantAlpha()
				{
					if( AGen is ConstantAlphaGen )
						return true;

					if( AGen is WaveAlphaGen && IsConstantWave( ((WaveAlphaGen)AGen).Waveform ) )
						return true;

					return false;
				}

				public bool IsConstant()
				{
					return IsConstantRgb() && IsConstantAlpha();
				}

				private bool IsConstantWave( Waveform waveform )
				{
					return waveform.Amplitude == 0 ||
						waveform.Frequency == 0;
				}

				public override int GetHashCode()
				{
					return RgbGen.GetHashCode() ^ AGen.GetHashCode();
				}

				public bool Equals( ColorSource other )
				{
					if( other == null )
						return false;

					return other.RgbGen.Equals( RgbGen ) &&
						other.AGen.Equals( AGen );
				}

				public override bool Equals( object obj )
				{
					return Equals( obj as ColorSource );
				}
			}

			#endregion

			private const int MaxClInterpolators = 2;
			private const int MaxTcInterpolators = 8;

			private RegisterAllocator tcInterpolators = new RegisterAllocator( MaxTcInterpolators );
			private RegisterAllocator clInterpolators = new RegisterAllocator( MaxClInterpolators );

			private Dictionary<TexCoordSource, Register> texCoordRegisters = new Dictionary<TexCoordSource, Register>( TexCoordSource.Comparer.Default );
			private Dictionary<ColorSource, Helpers.Pair<Register, InterpolatorSet>> colorRegisters = new Dictionary<ColorSource, Helpers.Pair<Register, InterpolatorSet>>();

			private struct Register2
			{
				public Register X, Y;
			}

			private Dictionary<TexCoordSource, Register2> auxPomRegisters = new Dictionary<TexCoordSource, Register2>( TexCoordSource.Comparer.Default );
			private Dictionary<TexLookupSource, string> texLookupNames = new Dictionary<TexLookupSource, string>( TexLookupSource.Comparer.Default );
			private Dictionary<TextureBundle, string> bundleLookupNames = new Dictionary<TextureBundle, string>();

			private EffectWriter vertexShaderBody = new EffectWriter();
			private EffectWriter pixelShaderBody = new EffectWriter();

			private ShaderStage baseStage;

			private bool shader_3_0;
			
			private Register pomNorm;
			private Register pomView;

			private ShaderCompiler owner;
			private int passIndex;

			public PassCompiler( ShaderCompiler owner, int index )
			{
				this.owner = owner;
				this.passIndex = index;
			}

			private PassCompiler()
			{
				//used internally by Clone()
			}

			public PassCompiler Clone()
			{
				PassCompiler ret = new PassCompiler();

				ret.owner = owner;
				ret.passIndex = passIndex;

				ret.tcInterpolators = tcInterpolators.Clone();
				ret.clInterpolators = clInterpolators.Clone();

				ret.texCoordRegisters = new Dictionary<TexCoordSource, Register>( texCoordRegisters, texCoordRegisters.Comparer );
				ret.auxPomRegisters = new Dictionary<TexCoordSource, Register2>( auxPomRegisters, auxPomRegisters.Comparer );
				ret.colorRegisters = new Dictionary<ColorSource, Helpers.Pair<Register, InterpolatorSet>>( colorRegisters, colorRegisters.Comparer );

				ret.texLookupNames = new Dictionary<TexLookupSource, string>( texLookupNames, texLookupNames.Comparer );
				ret.bundleLookupNames = new Dictionary<TextureBundle, string>( bundleLookupNames, bundleLookupNames.Comparer );

				ret.vertexShaderBody = vertexShaderBody.Clone();
				ret.pixelShaderBody = pixelShaderBody.Clone();

				ret.baseStage = baseStage;

				ret.shader_3_0 = shader_3_0;
				
				ret.pomNorm = pomNorm;
				ret.pomView = pomView;

				return ret;
			}

			public int PassIndex { get { return passIndex; } }
			public ShaderStage BaseStage { get { return baseStage; } }
			public bool HasPomModifier { get { return auxPomRegisters.Count != 0; } }

			#region Interpolator Allocation

			private Register AllocateTexCoordRegister( TexCoordSource source )
			{
				Register ret;
				if( texCoordRegisters.TryGetValue( source, out ret ) )
					return ret;

				Register? newReg = tcInterpolators.Allocate( source.Generator.TexCoordDimensions );
				if( !newReg.HasValue )
					throw new Exception( "Unable to find a free interpolator." );

				ret = newReg.Value;
				texCoordRegisters.Add( source, ret );

				return ret;
			}

			private Helpers.Pair<Register, InterpolatorSet> AllocateColorRegister( ColorSource source )
			{
				Helpers.Pair<Register, InterpolatorSet> ret;
				if( colorRegisters.TryGetValue( source, out ret ) )
					return ret;

				int elems = 0;
				
				if( !source.IsConstantRgb() )
					elems += source.IsGrayRgb() ? 1 : 3;

				if( !source.IsConstantAlpha() )
					elems += 1;

				if( elems == 0 )
					return new Helpers.Pair<Register, InterpolatorSet>( new Register(), InterpolatorSet.None );

				InterpolatorSet src = InterpolatorSet.Color;
				Register? reg = clInterpolators.Allocate( elems );

				if( !reg.HasValue )
				{
					reg = tcInterpolators.Allocate( elems );
					src = InterpolatorSet.TexCoord;
				}

				if( !reg.HasValue )
					throw new Exception( "Unable to find a free interpolator." );

				ret = new Helpers.Pair<Register,InterpolatorSet>( reg.Value, src );
				colorRegisters.Add( source, ret );

				return ret;
			}

			#endregion

			public void BeginPass( Shader shader )
			{
				EmitDeforms( shader );
			}

			private static readonly string[] BlendFactors = new string[]
			{
				"!!Invalid!!",
				"0", "1",
				"cl", "(1 - cl)",
				"cl.a", "(1 - cl.a)",
				"oCl.a", "(1 - oCl.a)",
				"oCl", "(1 - oCl)",
				"!!Invalid!!",
				"!!Invalid!!", "!!Invalid!!",
				"!!Invalid!!", "!!Invalid!!",
				"!!Invalid!!", "!!Invalid!!",
			};

			public void CompileStage( ShaderStage stage )
			{
				if( baseStage != null &&
					stage.AlphaTest.Enable && stage.AlphaTest.Function == CompareFunction.Never )
					return;

				EmitTextureLookups( stage );

				pixelShaderBody.WriteLine( "{{" );

				EmitColorSource( stage );
				EmitTextureBlends( stage );

				if( baseStage == null || !stage.AlphaBlend.Enable )
				{
					pixelShaderBody.WriteLine( "oCl = cl;" );
					baseStage = stage;
				}
				else
				{
					if( stage.AlphaTest.Enable )
					{
						float alphaRef = (float)stage.AlphaTest.Reference / 0xFF;

						switch( stage.AlphaTest.Function )
						{
							case CompareFunction.Always:
								break;

							case CompareFunction.Equal:
								pixelShaderBody.WriteLine( "if( cl.a == {0} )", alphaRef );
								break;

							case CompareFunction.NotEqual:
								pixelShaderBody.WriteLine( "if( cl.a != {0} )", alphaRef );
								break;

							case CompareFunction.Greater:
								pixelShaderBody.WriteLine( "if( cl.a > {0} )", alphaRef );
								break;

							case CompareFunction.GreaterEqual:
								pixelShaderBody.WriteLine( "if( cl.a >= {0} )", alphaRef );
								break;

							case CompareFunction.Less:
								pixelShaderBody.WriteLine( "if( cl.a < {0} )", alphaRef );
								break;

							case CompareFunction.LessEqual:
								pixelShaderBody.WriteLine( "if( cl.a <= {0} )", alphaRef );
								break;
						}
					}

					pixelShaderBody.WriteLine( "oCl = cl * {0} + oCl * {1};", BlendFactors[(int)stage.AlphaBlend.SourceBlend],
						BlendFactors[(int)stage.AlphaBlend.DestBlend] );
				}

				pixelShaderBody.WriteLine( "}}" );
			}

			public void CompileDepthStage()
			{
				Register depthReg = AllocateTexCoordRegister( new TexCoordSource( new ViewPositionTexCoordGen() ) );
				pixelShaderBody.WriteLine( "oCl = (length( {0} ) - DepthRange.x) / (DepthRange.y - DepthRange.x);", depthReg.ToString( "iTc" ) );
			}

			public void CompletePass()
			{
				EmitColorGens();
				EmitTcGens();
			}

			public void WriteVertexShader( EffectWriter writer )
			{
				WriteVertexShaderHeader( writer );
				WriteVertexShaderPrologue( writer );

				writer.Write( vertexShaderBody );

				WriteVertexShaderEpilogue( writer );
			}

			public void WritePixelShader( EffectWriter writer )
			{
				WritePixelShaderHeader( writer );
				WritePixelShaderPrologue( writer );

				writer.Write( pixelShaderBody );

				WritePixelShaderEpilogue( writer );
			}

			#region Vertex Shader Internals

			public string VertexShaderName { get { return string.Format( "pass{0}_{1}_vsh", owner.techniques.Count, passIndex ); } }
			public string VertexShaderModel { get { return shader_3_0 ? "vs_3_0" : "vs_2_0"; } }

			private void WriteVertexShaderHeader( EffectWriter writer )
			{
				writer.Write( "float4 {0}( float3 pos : POSITION, float3 norm : NORMAL, float2 iTcBase : TEXCOORD0, float2 iTcLm : TEXCOORD1, float4 iCl : COLOR", VertexShaderName );

				if( auxPomRegisters.Count != 0 )
					writer.Write( ", float3 tan : TANGENT, float3 bin : BINORMAL" );

				foreach( Register reg in tcInterpolators.UsedRegisters )
					writer.Write( ", out float{1} oTc{0} : TEXCOORD{0}", reg.Index, GetSwizzleRank( reg.Swizzle ) );

				foreach( Register reg in clInterpolators.UsedRegisters )
					writer.Write( ", out float{1} oCl{0} : COLOR{0}", reg.Index, GetSwizzleRank( reg.Swizzle ) );

				writer.WriteLine( " ) : POSITION" );
			}

			private void WriteVertexShaderPrologue( EffectWriter writer )
			{
				writer.WriteLine( "{{" );

				if( auxPomRegisters.Count != 0 )
				{
					writer.WriteLine( "float3 wNorm = mul( norm, (float3x3)MatModel );" );
					writer.WriteLine( "float3 wTan = mul( tan, (float3x3)MatModel );" );
					writer.WriteLine( "float3 wBin = mul( bin, (float3x3)MatModel );" );

					writer.WriteLine( "wNorm = normalize( wNorm );" );
					writer.WriteLine( "wTan = normalize( wTan );" );
					writer.WriteLine( "wBin = normalize( wBin );" );

					writer.WriteLine( "float3 wPos = mul( float4( pos, 1 ), (float3x4)MatModel );" );
					writer.WriteLine( "float3 wView = WorldCameraPos - wPos;" );

					writer.WriteLine( "{0} = wNorm;", pomNorm.ToString( "oTc" ) );
					writer.WriteLine( "{0} = wView;", pomView.ToString( "oTc" ) );
				}
			}

			private void WriteVertexShaderEpilogue( EffectWriter writer )
			{
				writer.WriteLine( "return mul( float4( pos, 1 ), MatMvp );" );
				writer.WriteLine( "}}" );
			}

			private void EmitDeforms( Shader shader )
			{
				foreach( VertexDeformation deformation in shader.Deformations )
				{
					if( deformation is WaveDeformation )
					{
						WaveDeformation def = (WaveDeformation)deformation;
						vertexShaderBody.WriteLine( "{{" );

						vertexShaderBody.WriteLine( "float amp, ofs = dot( pos, 1 ) * {0};", def.Spread );
						CompileWaveform( vertexShaderBody, "amp", def.Waveform, "ofs" );
						vertexShaderBody.WriteLine( "pos += norm * amp;" );

						vertexShaderBody.WriteLine( "}}" );

						continue;
					}
				}
			}

			private void EmitColorGens()
			{
				foreach( ColorSource source in colorRegisters.Keys )
				{
					if( source.IsConstantRgb() && source.IsConstantAlpha() )
						return;

					Helpers.Pair<Register, InterpolatorSet> colorReg = AllocateColorRegister( source );

					string regBase;
					switch( colorReg.Second )
					{
					case InterpolatorSet.Color:
						regBase = "oCl";
						break;

					case InterpolatorSet.TexCoord:
						regBase = "oTc";
						break;

					default:
						throw new Exception( "Messed up interpolator set." );
					}

					int swizIdx = 0;
					if( source.RgbGen is VertexColorGen )
					{
						VertexColorGen vCgen = source.RgbGen as VertexColorGen;

						vertexShaderBody.WriteLine( "{0}{1}.{2} = iCl.rgb;", regBase, colorReg.First.Index, GetSwizzleRange( colorReg.First.Swizzle, swizIdx, 3 ) );
						swizIdx += 3;
					}
					else if( source.RgbGen is WaveColorGen )
					{
						WaveColorGen wCgen = source.RgbGen as WaveColorGen;

						vertexShaderBody.WriteLine( "{{" );
						vertexShaderBody.WriteLine( "float wv;" );

						CompileWaveform( vertexShaderBody, "wv", wCgen.Waveform );

						vertexShaderBody.WriteLine( "{0}{1}.{2} = wv;", regBase, colorReg.First.Index, GetSwizzleRange( colorReg.First.Swizzle, swizIdx, 1 ) );
						swizIdx += 1;

						vertexShaderBody.WriteLine( "}}" );
					}

					if( source.AGen is VertexAlphaGen )
					{
						VertexAlphaGen vAgen = source.AGen as VertexAlphaGen;

						vertexShaderBody.WriteLine( "{0}{1}.{2} = iCl.a;", regBase, colorReg.First.Index, GetSwizzleRange( colorReg.First.Swizzle, swizIdx, 1 ) );
						swizIdx += 1;
					}
					else if( source.AGen is WaveAlphaGen )
					{
						WaveAlphaGen wAgen = source.AGen as WaveAlphaGen;

						vertexShaderBody.WriteLine( "{{" );
						vertexShaderBody.WriteLine( "float wv;" );

						CompileWaveform( vertexShaderBody, "wv", wAgen.Waveform );

						vertexShaderBody.WriteLine( "{0}{1}.{2} = wv;", regBase, colorReg.First.Index, GetSwizzleRange( colorReg.First.Swizzle, swizIdx, 1 ) );
						swizIdx += 1;

						vertexShaderBody.WriteLine( "}}" );
					}
				}
			}

			private sealed class ViewPositionTexCoordGen : TexCoordGen
			{
				public override int TexCoordDimensions { get { return 3; } }

				public override TexCoordGen Clone()
				{
					return this; //immutable
				}
			}

			private sealed class SpecialTexCoordGen : TexCoordGen
			{
				private int n;
				public SpecialTexCoordGen( int n )
				{
					this.n = n;
				}

				public override int TexCoordDimensions { get { return n; } }

				public override TexCoordGen Clone()
				{
					return new SpecialTexCoordGen( n );
				}

				public override int GetHashCode()
				{
					return base.GetHashCode();
				}

				public override bool Equals( object obj )
				{
					//assume I know what I'm doing and don't want to collapse the bastards onto each other
					return object.ReferenceEquals( this, obj );
				}
			}

			private void EmitTcGens()
			{
				foreach( KeyValuePair<TexCoordSource, Register> texReg in texCoordRegisters )
				{
					string regName = texReg.Value.ToString( "oTc" );

					if( texReg.Key.Generator is TextureTexCoordGen )
					{
						vertexShaderBody.WriteLine( "{0} = iTcBase;", regName );
					}
					else if( texReg.Key.Generator is LightmapTexCoordGen )
					{
						vertexShaderBody.WriteLine( "{0} = iTcLm;", regName );
					}
					else if( texReg.Key.Generator is EnvironmentMappedTexCoordGen )
					{
						vertexShaderBody.WriteLine( "{{ float3 v = normalize( WorldCameraPos - pos ); float3 refl = reflect( v, norm ); {0} = float2( 0.5 + refl.y * 0.5, 0.5 - refl.z * 0.5 ); }}", regName );
					}
					else if( texReg.Key.Generator is VectorTexCoordGen )
					{
						VectorTexCoordGen vTcGen = texReg.Key.Generator as VectorTexCoordGen;
						vertexShaderBody.WriteLine( "{0} = float2( dot( pos, {1}, dot( pos, {2} );",
							regName, vTcGen.VectorU, vTcGen.VectorV );
					}
					else if( texReg.Key.Generator is ViewPositionTexCoordGen )
					{
						vertexShaderBody.WriteLine( "{0} = mul( float4( pos, 1 ), MatModelView );", regName );
					}

					foreach( TexCoordMod tcMod in texReg.Key.Modifiers )
					{
						if( tcMod is PomTexCoordMod )
						{
							PomTexCoordMod mod = (PomTexCoordMod)tcMod;

							Register2 pomDirReg = auxPomRegisters[texReg.Key];

							vertexShaderBody.WriteLine( "{{" );

							vertexShaderBody.WriteLine( "float3 tsView;" );
							vertexShaderBody.WriteLine( "float2 pomDir;" );
							vertexShaderBody.WriteLine( "POM_ComputeParams( wView, wNorm, wTan, wBin, {0}, tsView, pomDir );", mod.BumpDepth );

							vertexShaderBody.WriteLine( "{0} = pomDir.x;", pomDirReg.X.ToString( "oTc" ) );
							vertexShaderBody.WriteLine( "{0} = pomDir.y;", pomDirReg.Y.ToString( "oTc" ) );

							vertexShaderBody.WriteLine( "}}" );

							break;
						}

						if( tcMod is TransformTexCoordMod )
						{
							TransformTexCoordMod mod = (TransformTexCoordMod)tcMod;
							vertexShaderBody.WriteLine( "{0} = float2( dot( float3( {0}, 1 ), {1} ), dot( float3( {0}, 1 ), {2} ) );",
								regName, new Vector3( mod.Matrix.M11, mod.Matrix.M12, mod.Matrix.M13 ),
								new Vector3( mod.Matrix.M21, mod.Matrix.M22, mod.Matrix.M23 ) );
						}
						else if( tcMod is RotateTexCoordMod )
						{
							RotateTexCoordMod mod = (RotateTexCoordMod)tcMod;

							vertexShaderBody.WriteLine( "{{" );

							vertexShaderBody.WriteLine( "float rotTime = -fmod( Time * {0}, 360 ) * {1};", mod.Speed, Math.PI / 180 );
							vertexShaderBody.WriteLine( "float rotSin, rotCos; sincos( rotTime, rotSin, rotCos );" );
							vertexShaderBody.WriteLine( "{0} = float2( dot( {0}, float2( rotCos, -rotSin ) ), dot( {0}, float2( rotSin, rotCos ) ) );", regName );
							vertexShaderBody.WriteLine( "{0} += 0.5 - 0.5 * float2( rotCos - rotSin, rotSin + rotCos );", regName );

							vertexShaderBody.WriteLine( "}}" );
						}
						else if( tcMod is StretchTexCoordMod )
						{
							StretchTexCoordMod mod = (StretchTexCoordMod)tcMod;

							vertexShaderBody.WriteLine( "{{" );

							vertexShaderBody.WriteLine( "float wv;" );
							CompileWaveform( vertexShaderBody, "wv", mod.Waveform );
							vertexShaderBody.WriteLine( "wv = 1 / wv;" );
							vertexShaderBody.WriteLine( "{0} = {0} * wv + 0.5 - 0.5 * wv;", regName );

							vertexShaderBody.WriteLine( "}}" );
						}
						else if( tcMod is ScrollTexCoordMod )
						{
							ScrollTexCoordMod mod = (ScrollTexCoordMod)tcMod;

							vertexShaderBody.WriteLine( "{0} += frac( {1} * Time );", regName, mod.Speed );
						}
					}
				}
			}

			#endregion

			#region Pixel Shader Internals

			public string PixelShaderName { get { return string.Format( "pass{0}_{1}_psh", owner.techniques.Count, passIndex ); } }
			public string PixelShaderModel { get { return shader_3_0 ? "ps_3_0" : "ps_2_0"; } }

			private void WritePixelShaderHeader( EffectWriter writer )
			{
				writer.Write( "float4 {0}( ", PixelShaderName );

				bool firstParam = true;

				foreach( Register reg in tcInterpolators.UsedRegisters )
				{
					if( !firstParam )
						writer.Write( ", " );
					firstParam = false;

					writer.Write( "float{1} iTc{0} : TEXCOORD{0}", reg.Index, GetSwizzleRank( reg.Swizzle ) );
				}

				foreach( Register reg in clInterpolators.UsedRegisters )
				{
					if( !firstParam )
						writer.Write( ", " );
					firstParam = false;
				
					writer.Write( "float{1} iCl{0} : COLOR{0}", reg.Index, GetSwizzleRank( reg.Swizzle ) );
				}

				writer.WriteLine( " ) : COLOR" );
			}

			private void WritePixelShaderPrologue( EffectWriter writer )
			{
				writer.WriteLine( "{{" );
				writer.WriteLine( "float4 oCl;" );
			}

			private void WritePixelShaderEpilogue( EffectWriter writer )
			{
				writer.WriteLine( "return oCl;" );
				writer.WriteLine( "}}" );
			}

			private void EmitColorSource( ShaderStage stage )
			{
				ColorSource source = new ColorSource( stage.RgbGen, stage.AlphaGen );
				Helpers.Pair<Register, InterpolatorSet> colorReg = AllocateColorRegister( source );

				pixelShaderBody.WriteLine( "float4 cl;" );

				if( source.IsConstantRgb() )
				{
					ConstantColorGen cRgbGen = (ConstantColorGen)stage.RgbGen;
					pixelShaderBody.WriteLine( "cl.rgb = {0};", cRgbGen.Color.ToVector3() );
				};

				if( source.IsConstantAlpha() )
				{
					ConstantAlphaGen cAGen = (ConstantAlphaGen)stage.AlphaGen;
					pixelShaderBody.WriteLine( "cl.a = {0};", (float)cAGen.Alpha / 0xFF );
				}

				string regBase;
				switch( colorReg.Second )
				{
				case InterpolatorSet.None:
					goto end;

				case InterpolatorSet.Color:
					regBase = "iCl";
					break;

				case InterpolatorSet.TexCoord:
					regBase = "iTc";
					break;

				default:
					throw new Exception( "Invalid interpolator set." );
				}

				if( !source.IsConstantRgb() )
				{
					if( source.IsGrayRgb() )
					{
						pixelShaderBody.WriteLine( "cl.rgb = {0}{1}.{2};", regBase, colorReg.First.Index, GetSwizzleRange( colorReg.First.Swizzle, 0, 1 ) );
						if( !source.IsConstantAlpha() )
							pixelShaderBody.WriteLine( "cl.a = {0}{1}.{2};", regBase, colorReg.First.Index, GetSwizzleRange( colorReg.First.Swizzle, 1, 1 ) );
					}
					else
					{
						pixelShaderBody.WriteLine( "cl.rgb = {0}{1}.rgb;", regBase, colorReg.First.Index );
						if( !source.IsConstantAlpha() )
							pixelShaderBody.WriteLine( "cl.a = {0}{1}.a;", regBase, colorReg.First.Index );
					}
				}
				else if( !source.IsConstantAlpha() )
				{
					pixelShaderBody.WriteLine( "cl.a = {0};", colorReg.First.ToString( regBase ) );
				}

			end:
				if( source.InvertRgb )
				{
					pixelShaderBody.WriteLine( "cl.rgb = 1 - cl.rgb;" );
				}

				if( source.InvertAlpha )
				{
					pixelShaderBody.WriteLine( "cl.a = 1 - cl.a;" );
				}
			}

			private void EmitTextureLookups( ShaderStage stage )
			{
				foreach( var bundle in stage.Bundles )
				{
					if( bundle.IsConstantBundle() )
						//embedded inline later
						continue;

					string bundleSampler = owner.samplerCompiler.GetSamplerName( bundle );
					TexLookupSource source = new TexLookupSource( bundle, bundleSampler );
					string bundleTcRegister = AllocateTexCoordRegister( source ).ToString( "iTc" );

					string lookupName;
					if( texLookupNames.TryGetValue( source, out lookupName ) )
					{
						//already handled this particular gen/mod combo
						bundleLookupNames[bundle] = lookupName;
						continue;
					}

					lookupName = string.Format( "tcLookup{0}", texLookupNames.Count );
					texLookupNames.Add( source, lookupName );
					bundleLookupNames[bundle] = lookupName;

					pixelShaderBody.WriteLine( "float4 {0};", lookupName );

					if( bundle.IsPomBundle() )
					{
						PomTexCoordMod pom = (PomTexCoordMod)bundle.TexCoordMods[0];

						if( auxPomRegisters.Count == 0 )
						{
							//first POM, set these up
							pomNorm = AllocateTexCoordRegister( new TexCoordSource( new SpecialTexCoordGen( 3 ) ) );
							pomView = AllocateTexCoordRegister( new TexCoordSource( new SpecialTexCoordGen( 3 ) ) );
						}

						Register2 pomDirRegs;
						if( !auxPomRegisters.TryGetValue( source, out pomDirRegs ) )
						{
							pomDirRegs.X = AllocateTexCoordRegister( new TexCoordSource( new SpecialTexCoordGen( 1 ) ) );
							pomDirRegs.Y = AllocateTexCoordRegister( new TexCoordSource( new SpecialTexCoordGen( 1 ) ) );
							
							auxPomRegisters.Add( source, pomDirRegs );
						}

						shader_3_0 = true;

						pixelShaderBody.WriteLine( "{{" );

						pixelShaderBody.WriteLine( "float3 pomNorm = normalize( {0} );", pomNorm.ToString( "iTc" ) );
						pixelShaderBody.WriteLine( "float3 pomView = normalize( {0} );", pomView.ToString( "iTc" ) );
						pixelShaderBody.WriteLine( "float ndv = dot( pomNorm, pomView );" );
						pixelShaderBody.WriteLine( "float2 pomDir = float2( {0}, {1} );", pomDirRegs.X.ToString( "iTc" ), pomDirRegs.Y.ToString( "iTc" ) );

						SamplerCompiler.TextureObject bumpTexObj;
						string bumpSampler = owner.samplerCompiler.GetBumpSampler( pom.BumpTextureName, out bumpTexObj );

						pixelShaderBody.WriteLine( "float2 tc = POM_Trace( {0}, {1}, {2}, ndv, pomDir, ParallaxMipCutoff, ParallaxStepBounds );",
							bumpSampler, bumpTexObj.DimensionBindingName, bundleTcRegister );

						pixelShaderBody.WriteLine( "{0} = tex2D( {1}, tc );", lookupName, bundleSampler );

						pixelShaderBody.WriteLine( "}}" );
					}
					else
					{
						pixelShaderBody.WriteLine( "{0} = tex2D( {1}, {2} );", lookupName, bundleSampler, bundleTcRegister );
					}
				}
			}

			private void EmitTextureBlends( ShaderStage stage )
			{
				for( int b = 0; b < stage.Bundles.Count; b++ )
				{
					TextureBundle bundle = stage.Bundles[b];
					
					Vector4 constColor;
					if( bundle.IsConstantBundle( out constColor ) )
					{
						//bundle is white image -- update if that list ever grows!!
						pixelShaderBody.WriteLine( "float4 t{0} = {1};", b, constColor );
					}
					else
					{
						pixelShaderBody.WriteLine( "float4 t{0} = {1};", b,
							bundleLookupNames[bundle] );
					}

					//blend it
					EmitTextureBlendSource( b, 0, bundle.TextureBlend );
					EmitTextureBlendSource( b, 1, bundle.TextureBlend );

					switch( bundle.TextureBlend.ColorOperation )
					{
					case TextureBlendOp.Replace:
						pixelShaderBody.Write( "cl.rgb = (ba{0}_0.rgb)", b );
						break;

					case TextureBlendOp.Add:
						pixelShaderBody.Write( "cl.rgb = (ba{0}_0.rgb + ba{0}_1.rgb)", b );
						break;

					case TextureBlendOp.Subtract:
						pixelShaderBody.Write( "cl.rgb = (ba{0}_0.rgb - ba{0}_1.rgb)", b );
						break;

					case TextureBlendOp.Modulate:
						pixelShaderBody.Write( "cl.rgb = (ba{0}_0.rgb * ba{0}_1.rgb)", b );
						break;
					}

					if( bundle.TextureBlend.ColorScale != 1 )
						pixelShaderBody.WriteLine( " * {0};", bundle.TextureBlend.ColorScale );
					else
						pixelShaderBody.WriteLine( ";" );

					switch( bundle.TextureBlend.AlphaOperation )
					{
					case TextureBlendOp.Replace:
						pixelShaderBody.Write( "cl.a = (ba{0}_0.a)", b );
						break;

					case TextureBlendOp.Add:
						pixelShaderBody.Write( "cl.a = (ba{0}_0.a + ba{0}_1.a)", b );
						break;

					case TextureBlendOp.Subtract:
						pixelShaderBody.Write( "cl.a = (ba{0}_0.a - ba{0}_1.a)", b );
						break;

					case TextureBlendOp.Modulate:
						pixelShaderBody.Write( "cl.a = (ba{0}_0.a * ba{0}_1.a)", b );
						break;
					}

					if( bundle.TextureBlend.AlphaScale != 1 )
						pixelShaderBody.WriteLine( " * {0};", bundle.TextureBlend.AlphaScale );
					else
						pixelShaderBody.WriteLine( ";" );
				}
			}

			private void EmitTextureBlendSource( int b, int i, TextureBlend blend )
			{
				TextureBlendSource source = i == 0 ? blend.Source0 : blend.Source1;

				pixelShaderBody.WriteLine( "float4 ba{0}_{1};", b, i );

				//emit color

				pixelShaderBody.Write( "ba{0}_{1}.rgb = ", b, i );

				string src;
				switch( source.ColorSource )
				{
				case TextureBlendSourceSrc.Previous:
					src = "cl";
					break;

				case TextureBlendSourceSrc.Constant:
					src = string.Format( "{0:rgba}", blend.ConstantColor );
					break;

				case TextureBlendSourceSrc.Primary:
					src = "iCl";
					break;

				case TextureBlendSourceSrc.Texture:
					src = string.Format( "t{0}", b );
					break;

				default:
					throw new Exception( "Invalid color source." );
				}

				switch( source.ColorOperation )
				{
				case TextureBlendSourceColorOp.Color:
					pixelShaderBody.WriteLine( "{0}.rgb;", src );
					break;

				case TextureBlendSourceColorOp.InverseColor:
					pixelShaderBody.WriteLine( "1 - {0}.rgb;", src );
					break;

				case TextureBlendSourceColorOp.Alpha:
					pixelShaderBody.WriteLine( "{0}.aaa;", src );
					break;

				case TextureBlendSourceColorOp.InverseAlpha:
					pixelShaderBody.WriteLine( "1 - {0}.aaa;", src );
					break;

				default:
					throw new Exception( "Invalid color operation." );
				}

				//emit alpha

				pixelShaderBody.Write( "ba{0}_{1}.a = ", b, i );

				switch( source.AlphaSource )
				{
				case TextureBlendSourceSrc.Previous:
					src = "cl";
					break;

				case TextureBlendSourceSrc.Constant:
					src = string.Format( "{0:rgba}", blend.ConstantColor );
					break;

				case TextureBlendSourceSrc.Primary:
					src = "iCl";
					break;

				case TextureBlendSourceSrc.Texture:
					src = string.Format( "t{0}", b );
					break;

				default:
					throw new Exception( "Invalid alpha source." );
				}

				switch( source.AlphaOperation )
				{
				case TextureBlendSourceAlphaOp.Alpha:
					pixelShaderBody.WriteLine( "{0}.a;", src );
					break;

				case TextureBlendSourceAlphaOp.InverseAlpha:
					pixelShaderBody.WriteLine( "1 - {0}.a;", src );
					break;

				default:
					throw new Exception( "Invalid color operation." );
				}
			}

			#endregion
		}

		#region Waveform

		private static void CompileWaveform( EffectWriter writer, string outName, Waveform wave, string offsetName )
		{
			if( wave.Amplitude == 0 )
			{
				writer.WriteLine( "{0} = {1};", outName, wave.Base );
				return;
			}

			writer.WriteLine( "{{" );

			writer.WriteLine( "float wt = {0} * Time + {1};", wave.Frequency, wave.Phase );

			if( offsetName != null )
				writer.WriteLine( "wt += {0};", offsetName );

			writer.WriteLine( "wt = frac( wt );" );

			switch( wave.Function )
			{
			case WaveFunction.Noise:
				//ToDo!!!
				writer.WriteLine( "{0} = {1};", outName, wave.Base );
				break;

			case WaveFunction.Sine:
				writer.WriteLine( "{0} = sin( wt * {1} );", outName, MathHelper.TwoPi );
				break;

			case WaveFunction.Square:
				writer.WriteLine( "{0} = (wt < 0.5) ? 1 : -1;", outName );
				break;

			case WaveFunction.Sawtooth:
				writer.WriteLine( "{0} = wt;", outName );
				break;

			case WaveFunction.InverseSawtooth:
				writer.WriteLine( "{0} = 1 - wt;", outName );
				break;

			case WaveFunction.Triangle:
				writer.WriteLine( "if( wt < 0.25 ) {0} = wt * 4;", outName );
				writer.WriteLine( "else if( wt < 0.75 ) {0} = 1 - (wt - 0.25) * 4;", outName );
				writer.WriteLine( "else {0} = (wt - 0.75) * 4 - 1;", outName );
				break;
			}

			writer.WriteLine( "{0} = {1} + {2} * {0};", outName, wave.Base, wave.Amplitude );

			writer.WriteLine( "}}" );
		}

		private static void CompileWaveform( EffectWriter writer, string outName, Waveform wave )
		{
			CompileWaveform( writer, outName, wave, null );
		}

		#endregion

		#region Swizzle
		private static string GetSwizzleRange( Swizzle swiz, int first, int count )
		{
			while( first > 0 && swiz != Swizzle.Empty )
			{
				bool didwork = false;
				for( int i = 0; i < 4; i++ )
				{
					Swizzle elem = (Swizzle)(1 << i);

					if( (swiz & elem) != Swizzle.Empty )
					{
						swiz &= ~elem;
						didwork = true;
						break;
					}
				}

				if( didwork )
					first--;
			}

			string ret = string.Empty;

			if( count > 0 && (swiz & Swizzle.X) != Swizzle.Empty )
			{
				ret += "x";
				count--;
			}

			if( count > 0 && (swiz & Swizzle.Y) != Swizzle.Empty )
			{
				ret += "y";
				count--;
			}

			if( count > 0 && (swiz & Swizzle.Z) != Swizzle.Empty )
			{
				ret += "z";
				count--;
			}

			if( count > 0 && (swiz & Swizzle.W) != Swizzle.Empty )
			{
				ret += "w";
				count--;
			}

			return ret;
		}

		private static int GetSwizzleRank( Swizzle swiz )
		{
			if( (swiz & Swizzle.W) != Swizzle.Empty )
				return 4;

			if( (swiz & Swizzle.Z) != Swizzle.Empty )
				return 3;

			if( (swiz & Swizzle.Y) != Swizzle.Empty )
				return 2;

			if( (swiz & Swizzle.X) != Swizzle.Empty )
				return 1;

			return 0;
		}
		#endregion

		private SamplerCompiler samplerCompiler;
		private List<PassCompiler> passes = new List<PassCompiler>();
		private List<string> techniques = new List<string>();
		private bool hasPomTech;

		#region Shader Common

		private void WriteShaderGlobals( EffectWriter writer )
		{
			writer.WriteLine(
				@"shared float Time : TIME;
				shared float4x4 MatModel : MODEL;
				shared float4x4 MatModelView : MODELVIEW;
				shared float4x4 MatMvp : MODELVIEWPROJECTION;
				shared float3 ModelCameraPos : MODELCAMERAPOSITION;
				shared float3 WorldCameraPos : WORLDCAMERAPOSITION;
				shared float2 DepthRange : DEPTHRANGE;" );
			writer.WriteLine();
		}

		private void WriteShaderHeader( EffectWriter writer )
		{
			if( hasPomTech )
			{
				//should be externally settable
				writer.WriteLine( "int2 ParallaxStepBounds = int2( 10, 50 );" );
				writer.WriteLine( "int ParallaxMipCutoff = 3;" );

				writer.Append( Properties.Resources.POM_fxh );
			}

			samplerCompiler.EmitShaderText( writer );
		}

		private void WriteShaderTechniqueParams( EffectWriter writer, Shader shader )
		{
			writer.WriteLine( "CullMode = {0};", shader.Cull );

			//ToDo: depth bias
		}

		#endregion

		#region Sky Shader

		private void CompileSkyShader( EffectWriter writer, Shader shader )
		{
			writer.WriteLine( "texture SkyTexture;" );
			writer.WriteLine( "sampler {0} = sampler_state {{ Texture = <{1}>; AddressU = {2}; AddressV = {2}; MinFilter = Linear; MagFilter = Linear; MipFilter = Linear; }};",
				"SkySampler", "SkyTexture", TextureAddressMode.Clamp );
			writer.WriteLine();

			writer.WriteLine( "float4 sky_vs( float4 iPos : POSITION, out float3 oTc : TEXCOORD0 ) : POSITION {{" );
			writer.WriteLine( "    oTc = iPos.xyz - ModelCameraPos;" );
			writer.WriteLine( "    return mul( iPos, MatMvp ); }}" );
			writer.WriteLine();

			writer.WriteLine( "float4 sky_ps( float3 iTc : TEXCOORD0 ) : COLOR {{" );
			writer.WriteLine( "    return texCUBE( SkySampler, iTc ); }}" );
			writer.WriteLine();

			writer.WriteLine( "technique t0 < int Sort = {0}; > {{ pass p0 {{", (int)ShaderSort.Environment );
			writer.WriteLine( "    VertexShader = compile vs_2_0 sky_vs();" );
			writer.WriteLine( "    PixelShader = compile ps_2_0 sky_ps();" );
			writer.WriteLine( "    AlphaBlendEnable = False;" );
			writer.WriteLine( "    AlphaTestEnable = False;" );
			writer.WriteLine( "    ZEnable = True;" );
			writer.WriteLine( "    ZFunc = {0};", CompareFunction.LessEqual );
			writer.WriteLine( "    ZWriteEnable = True;" );
			
			WriteShaderTechniqueParams( writer, shader );
			
			writer.WriteLine( "}} }}" );
		}

		private void CompileSkyShader_Depth( EffectWriter writer, Shader shader )
		{
			writer.WriteLine( "float4 sky_vs( float4 iPos : POSITION, out float3 oPos : TEXCOORD0 ) : POSITION {{" );
			writer.WriteLine( "    oPos = mul( iPos, MatModelView );" );
			writer.WriteLine( "    return mul( iPos, MatMvp ); }}" );
			writer.WriteLine();

			writer.WriteLine( "float4 sky_ps( float3 iPos : TEXCOORD0 ) : COLOR {{" );
			writer.WriteLine( "    return (length( iPos ) - DepthRange.x) / (DepthRange.y - DepthRange.x); }}" );
			writer.WriteLine();

			writer.WriteLine( "technique t0 < int Sort = {0}; > {{ pass p0 {{", (int)ShaderSort.Environment );
			writer.WriteLine( "    VertexShader = compile vs_2_0 sky_vs();" );
			writer.WriteLine( "    PixelShader = compile ps_2_0 sky_ps();" );
			writer.WriteLine( "    AlphaBlendEnable = False;" );
			writer.WriteLine( "    AlphaTestEnable = False;" );
			writer.WriteLine( "    ZEnable = True;" );
			writer.WriteLine( "    ZFunc = {0};", CompareFunction.LessEqual );
			writer.WriteLine( "    ZWriteEnable = True;" );

			WriteShaderTechniqueParams( writer, shader );

			writer.WriteLine( "}} }}" );
		}

		#endregion

		#region Regular Shader

		private void WriteRegularShader( EffectWriter writer, Shader shader )
		{
			EmitCompiledPasses( writer );
			EmitTechnique( writer, shader );
		}

		private void CompileShaderStages_Depth( Shader shader )
		{
			PassCompiler pass = new PassCompiler( this, 0 );
			pass.BeginPass( shader );
			pass.CompileDepthStage();
			pass.CompletePass();
			passes.Add( pass );
		}

		private void CompileShaderStages( Shader shader )
		{
			for( int i = 0; i < shader.Stages.Count; i++ )
			{
				PassCompiler pass = new PassCompiler( this, i );

				pass.BeginPass( shader );
				pass.CompileStage( shader.Stages[i] );

				if( ShaderOptimizer.IsSolid( shader.Stages[i] ) )
				{
					for( int j = i + 1; j < shader.Stages.Count; j++ )
					{
						if( !CanLayer( shader.Stages[i], shader.Stages[j] ) )
							break;

						PassCompiler pass2 = pass.Clone();

						try
						{
							pass2.CompileStage( shader.Stages[j] );
						}
						catch
						{
							break;
						}

						pass = pass2;
						i = j;
					}
				}

				pass.CompletePass();
				passes.Add( pass );

				hasPomTech |= pass.HasPomModifier;
			}
		}

		private bool CanLayer( ShaderStage baseStage, ShaderStage topStage )
		{
			if( topStage.DepthTest.Enable != baseStage.DepthTest.Enable )
				return false;

			if( baseStage.DepthTest.Enable )
			{
				if( topStage.DepthTest.Function != baseStage.DepthTest.Function )
					return false;

				if( topStage.DepthTest.WriteEnable && !baseStage.DepthTest.WriteEnable )
					return false;
			}

			return true;
		}

		private void EmitCompiledPasses( EffectWriter writer )
		{
			foreach( PassCompiler pass in passes )
			{
				pass.WriteVertexShader( writer );
				pass.WritePixelShader( writer );
			}
		}

		private void EmitTechnique( EffectWriter writer, Shader shader )
		{
			ShaderSort sort = shader.Sort;
			if( sort == ShaderSort.Default )
			{
				if( shader.PolygonOffset != 0 )
					sort = ShaderSort.Decal;
				else if( shader.Stages.Count > 0 )
				{
					ShaderStage stage = shader.Stages[0];
					if( stage.AlphaBlend.Enable )
					{
						if( stage.DepthTest.Enable && stage.DepthTest.WriteEnable )
							sort = ShaderSort.SeeThrough;
						else
							sort = ShaderSort.Blend0;
					}
				}
			}

			if( sort == ShaderSort.Default )
				sort = ShaderSort.Opaque;

			writer.WriteLine( "technique t{0} < int Sort = {1}; > {{", techniques.Count, (int)sort );

			foreach( PassCompiler pass in passes )
			{
				writer.WriteLine( "pass p{0}", pass.PassIndex );
				writer.WriteLine( "{{" );

				writer.WriteLine( "VertexShader = compile {1} {0}();", pass.VertexShaderName, pass.VertexShaderModel );
				writer.WriteLine( "PixelShader = compile {1} {0}();", pass.PixelShaderName, pass.PixelShaderModel );

				ShaderStage stage = pass.BaseStage;

				if( stage != null )
				{
					if( stage.AlphaTest.Enable )
					{
						writer.WriteLine( "AlphaTestEnable = True;" );
						writer.WriteLine( "AlphaFunc = {0};", stage.AlphaTest.Function );
						writer.WriteLine( "AlphaRef = {0};", stage.AlphaTest.Reference );
					}
					else
					{
						writer.WriteLine( "AlphaTestEnable = False;" );
					}

					if( stage.AlphaBlend.Enable &&
						(stage.AlphaBlend.SourceBlend != Blend.One ||
						stage.AlphaBlend.DestBlend != Blend.Zero) )
					{
						writer.WriteLine( "AlphaBlendEnable = True;" );
						writer.WriteLine( "BlendOp = ADD;" );
						writer.WriteLine( "SrcBlend = {0};", stage.AlphaBlend.SourceBlend );
						writer.WriteLine( "DestBlend = {0};", stage.AlphaBlend.DestBlend );
					}
					else
					{
						writer.WriteLine( "AlphaBlendEnable = False;" );
					}

					if( stage.DepthTest.Enable )
					{
						writer.WriteLine( "ZEnable = True;" );
						writer.WriteLine( "ZFunc = {0};", stage.DepthTest.Function );
						writer.WriteLine( "ZWriteEnable = {0};", stage.DepthTest.WriteEnable );
					}
					else
					{
						writer.WriteLine( "ZEnable = False;" );
					}
				}
				else
				{
					writer.WriteLine( "AlphaBlendEnable = False;" );
					writer.WriteLine( "AlphaTestEnable = False;" );
					writer.WriteLine( "ZEnable = True;" );
					writer.WriteLine( "ZFunc = {0};", CompareFunction.LessEqual );
					writer.WriteLine( "ZWriteEnable = {0};", true );
				}

				WriteShaderTechniqueParams( writer, shader );

				writer.WriteLine( "}}" );
			}

			writer.WriteLine( "}}" );
		}

		#endregion

		public void BeginCompilation()
		{
			samplerCompiler = new SamplerCompiler();
			techniques.Clear();
			hasPomTech = false;
		}

		public void CompileDepthTechnique( Shader shader )
		{
			if( !ValidateShader( shader ) )
				throw new ArgumentException();

			EffectWriter techText = new EffectWriter();

			if( shader.Sky != null )
			{
				CompileSkyShader_Depth( techText, shader );
			}
			else
			{
				CompileShaderStages_Depth( shader );
				WriteRegularShader( techText, shader );
			}

			passes.Clear();

			techniques.Add( techText.ToString() );
		}

		public void CompileTechnique( Shader shader )
		{
			if( !ValidateShader( shader ) )
				throw new ArgumentException();

			EffectWriter techText = new EffectWriter();

			if( shader.Sky != null )
			{
				CompileSkyShader( techText, shader );
			}
			else
			{
				CompileShaderStages( shader );
				WriteRegularShader( techText, shader );
			}

			passes.Clear();
			techniques.Add( techText.ToString() );
		}

		public EffectContent EndCompilation( out Dictionary<TextureBundle, string> usedTextureObjects )
		{
			usedTextureObjects = new Dictionary<TextureBundle,string>();
			foreach( var tex in samplerCompiler.TextureBindings )
				usedTextureObjects[tex.Key] = tex.Value.Name;

			EffectWriter effectText = new EffectWriter();

			WriteShaderGlobals( effectText );
			WriteShaderHeader( effectText );

			foreach( var tech in techniques )
			{
				effectText.Append( "/*\n\tTechnique\n*/" );
				effectText.Append( tech.ToString() );
				effectText.Append( "\n\n" );
			}

			EffectContent ret = new EffectContent();
			ret.EffectCode = effectText.ToString();

			return ret;
		}

		public EffectContent EndCompilation()
		{
			Dictionary<TextureBundle, string> usedTextureObjects;
			return EndCompilation( out usedTextureObjects );
		}

		public static bool ValidateShader( Shader shader )
		{
			if( shader == null )
				throw new ArgumentNullException();

			foreach( var stage in shader.Stages )
			{
				foreach( var bundle in stage.Bundles )
				{
					int pomIdx = bundle.TexCoordMods.FindIndex( b => b is PomTexCoordMod );

					if( pomIdx == -1 )
						continue;

					if( bundle.TexCoordMods.Count != 1 || !(bundle.TexCoordGen is TextureTexCoordGen) )
						return false;
				}
			}

			return true;
		}
	}
}